package fr.vahren.processing;

import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.List;

import processing.core.PApplet;
import processing.core.PVector;
import traer.physics.Attraction;
import traer.physics.Particle;
import traer.physics.ParticleSystem;
import traer.physics.Spring;
import traer.physics.Vector3D;
import controlP5.ControlEvent;
import controlP5.ControlListener;
import controlP5.ControlP5;
import fr.vahren.processing.camera.VahrenCam;
import fr.vahren.processing.entity.Atom;
import fr.vahren.processing.entity.Electron;
import fr.vahren.processing.entity.Entity;
import fr.vahren.processing.picking.PickerDist;

public class Main extends PApplet implements ControlListener {

	public static final int ATOM_SIZE = 40;

	public static final int SIZE = 900;

	public static final int NB_ELECTRONS = 5;

	public static final int NB_ATOMS = 4;

	public static final float MAX_VELOCITY = 5;

	public static final int SPRING_DIST = ATOM_SIZE * 2;

	private static final int ATMOSPHERE_RADIUS = ATOM_SIZE * 3;

	private String text = "";

	private ParticleSystem sys;
	private List<Atom> atoms;

	private List<Entity> entities;

	public boolean ok = false;

	private VahrenCam cam;

	private PickerDist picker;

	private Entity picked;

	private PVector startDrag;

	private Atom startDragEntity;

	private ControlP5 controller;
	// CTRLP5 variables
	public int slider = 10;

	@Override
	public void setup() {
		size(SIZE, SIZE, OPENGL);
		controller = new ControlP5(this);
		controller.addSlider("slider", 0, 200, 20, 40, 400, 10);
		controller.addToggle("Toggle").setPosition(100, 100).setSize(200, 200);
		controller.setAutoDraw(false);
		
		cam = new VahrenCam(this);


		picker = new PickerDist(this);

		sys = new ParticleSystem(0, 0);
		entities = new ArrayList<>();
		atoms = new ArrayList<>();

		float x, y, z;
		for (int i = 0; i < NB_ATOMS; i++) {
			x = random(0, SIZE);
			y = random(0, SIZE);
			z = random(-100, 100);
			createAtomWithElectrons(x, y, z);
		}

		loaded();

	}

	@Override
	public void draw() {
		sys.tick();

		background(50);

		pick();

		for (Entity a : entities) {
			a.draw();
			checkTravel(a);
		}

		textSize(50);
		fill(255f);
		text(text, 100, 100, 0);

		// dragging

		lights();
		gui();

	}

	public void gui() {
	//		currCameraMatrix = new PMatrix3D(g3.camera);
	//		camera();
		// CTRLP5
		noLights();
		controller.update();
		controller.draw();

		if (startDrag != null) {
			stroke(0, 255, 0);
			line(startDrag.x, startDrag.y , mouseX, mouseY);
		}
		
//		g3.camera = currCameraMatrix;
	}

	private void createAtomWithElectrons(float x2, float y2, float z2) {
		Particle sun = sys.makeParticle(10, x2, y2, z2);
		// is anti on random
		Atom atom = new Atom(sun, this, random(2) > 1);
		entities.add(atom);
		atoms.add(atom);
		for (int i = 0; i < NB_ELECTRONS; i++) {
			spawnElectron(atom);
		}
	}

	private void spawnElectron(Atom atom) {
		Particle p;
		float x;
		float y;
		float z;
		x = random(-10, 10);
		z = random(-10, 10);
		y = random(-10, 10);
		p = sys.makeParticle(15, atom.x() + 2 * x, atom.y() + 2 * y, atom.z()
				+ z);
		sys.makeSpring(p, atom.getParticle(), 0.1f, 2, SPRING_DIST);

		// initial velocity
		Vector3D normal = getRandomNormal(p.position(), atom.getParticle()
				.position(), 8);
		p.velocity().set(normal);
		entities.add(new Electron(p, this, atom));
	}

	private void loaded() {
		synchronized (this) {
			ok = true;
			this.notifyAll();
		}
	}

	@Override
	public void mousePressed(MouseEvent e) {

		System.out.println("PRESSED");
		if (picked != null && e.getButton() == MouseEvent.BUTTON3
				&& picked instanceof Atom) {
			// right click begin
			startDrag = new PVector(mouseX, mouseY);
			startDragEntity = (Atom) picked;
		}

	}

	@Override
	public void mouseReleased(MouseEvent e) {
		System.out.println("RELEASED " + e.getButton());
		if (picked != null && e.getButton() == MouseEvent.BUTTON3) {
			if (picked instanceof Atom) {
				Atom target = (Atom) picked;
				List<Electron> electrons = new ArrayList<>(
						startDragEntity.getElectrons());
				for (Electron el : electrons) {
					sendElectronToAtom(el, target);
					slider++;
				}
			}
		}
		startDrag = null;
		startDragEntity = null;
	}

	@Override
	public void mouseClicked(MouseEvent e) {

		if (picked != null && e.getButton() == MouseEvent.BUTTON1) {
			zoomToAtom((Atom) picked);
		}
		System.out.println("CLICKED " + e.getButton());
	}

	private void pick() {
		Entity e = picker.pick(mouseX, mouseY);
		if (picked != null && !picked.equals(e)) {
			picked.pick();
		}
		picked = e;
		if (picked != null && !picked.isPicked()) {
			picked.pick();
		}
	}

	/**
	 * Called when an Electron moving to an atom is in the atmosphere of the
	 * atom.
	 */
	private void linkElectronToAtom(Electron e, Atom a) {
		// remove attractions
		for (int i = 0; i < sys.numberOfAttractions(); i++) {
			Attraction att = sys.getAttraction(i);
			if (att.getOneEnd().equals(e.getParticle())
					|| att.getTheOtherEnd().equals(e.getParticle())) {
				sys.removeAttraction(att);
			}
		}
		// check if atom contains anti
		if (a.containsOppositeElectrons(e.isAnti())) {

		} else {

			// add new spring
			createSpring(e, a);
			e.setTarget(null);
			e.setAtom(a);
		}
	}

	private void sendElectronToAtom(Electron e, Atom a) {
		// remove springs
		for (int i = 0; i < sys.numberOfSprings(); i++) {
			Spring spring = sys.getSpring(i);
			if (spring.getOneEnd().equals(e.getParticle())
					|| spring.getTheOtherEnd().equals(e.getParticle())) {
				sys.removeSpring(spring);
			}
		}
		// slow donw so it's not flying everywhere
		e.slowDown(5);
		e.sendImpulsion(vector(e, a), 5);

		// add new attraction
		createAttraction(e, a);
		e.removeAtom();
		e.setTarget(a);
	}

	private Vector3D vector(Entity e, Entity a) {
		return new Vector3D(a.x() - e.x(), a.y() - e.y(), a.z() - e.z());
	}

	private void createAttraction(Entity e, Entity a) {
		sys.makeAttraction(e.getParticle(), a.getParticle(), e.getSpeed(),
				ATMOSPHERE_RADIUS);
	}

	private void createSpring(Entity e, Entity a) {
		sys.makeSpring(e.getParticle(), a.getParticle(), 0.1f, 2, SPRING_DIST);
	}

	private Vector3D getRandomNormal(Vector3D position, Vector3D position2,
			int strenght) {
		Vector3D link = new Vector3D(position.x() - position2.x(), position.y()
				- position2.y(), position.z() - position2.z());
		float x2 = random(strenght);
		float y2 = random(strenght);
		float z2 = link.z() == 0 ? random(strenght) : (-link.x() * x2 - link
				.y() * y2)
				/ link.z();
		return new Vector3D(x2, y2, z2);
	}

	/**
	 * Checks for electrons if it is traveling (target != null) and then checks
	 * if it has arrived.
	 * 
	 * @param a
	 */
	private void checkTravel(Entity a) {
		if (a instanceof Electron) {
			Electron e = (Electron) a;
			if (e.getTarget() != null
					&& dist(e.x(), e.y(), e.z(), e.getTarget().x(), e
							.getTarget().y(), e.getTarget().z()) <= ATMOSPHERE_RADIUS) {
				linkElectronToAtom(e, e.getTarget());
			}
		}
	}

	private void zoomToAtom(Atom a1) {
		cam.lookAt(a1.x(), a1.y(), 0);
	}

	public void changeSprings(float length) {
		for (int i = 0; i < sys.numberOfSprings(); i++) {
			sys.getSpring(i).setRestLength(length);
		}
	}

	public PVector peasyScreenXYZ(float x, float y, float z) {
		pushMatrix();
		translate(x, y, z);
		float[] rota = new float[2];
		rotateX(rota[0]);
		rotateY(rota[1]);
		rotateZ(rota[2]);

		x = screenX(0, 0, 0);
		y = screenY(0, 0, 0);
		z = screenZ(0, 0, 0);
		popMatrix();
		return new PVector(x, y, z);
	}

	public static void main(String[] args) {
		PApplet.runSketch(args, new Main());
	}

	/**
	 * @return the entities
	 */
	public List<Entity> getEntities() {
		return entities;
	}

	/**
	 * @param entities
	 *            the entities to set
	 */
	public void setEntities(List<Entity> entities) {
		this.entities = entities;
	}

	@Override
	public void controlEvent(ControlEvent arg0) {
		System.out.println(arg0.toString());
	}

}
